package org.jspecial.tools;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
import org.apache.commons.io.FileUtils;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Main {
    private static final Logger LOG = LoggerFactory.getLogger(Main.class);

    public static void main(String[] args) throws IOException {
        Options opts = new Options();
        CmdLineParser parser = new CmdLineParser(opts);
        try {
            parser.parseArgument(args);
            Combiner combiner = new Combiner(opts.getClassPath());
            for (File sourceFile : opts.getSourceFiles()) {
                combiner.addSourceFile(sourceFile);
            }
            for (String className : opts.getMainClasses()) {
                combiner.addClass(className);
            }
            for (String className : opts.getExcludeClasses()) {
                combiner.excludeClass(className);
            }
            for (String arg : opts.getArguments()) {
                combiner.addSourceFile(new File(arg));
            }
            if (opts.getOutputFile() != null) {
                FileUtils.forceMkdir(opts.getOutputFile().getParentFile());
            }
            combiner.setUseStrict(opts.isUseStrict());
            Writer out = (opts.getOutputFile() != null)
                    ? new FileWriter(opts.getOutputFile())
                    : new PrintWriter(System.out);
            if (!CompressionType.none.equals(opts.getCompressionType())) {
                StringWriter js = new StringWriter();
                combiner.combine(js);
                compress(js.toString(), out, opts.getCompressionType());
            } else {
                combiner.combine(out);
            }
            out.close();
        } catch (CmdLineException e) {
            System.err.println(e.getMessage());
            System.err.println("java -jar jspecial.jar [options...] files...");
            parser.printUsage(System.err);
        }
    }

    private static void compress(String js, Writer out, CompressionType compressionType) throws IOException {
        JavaScriptCompressor compressor = new JavaScriptCompressor(
                new StringReader(js),
                new LogErrorReporter());
        boolean munge = !CompressionType.nomangle.equals(compressionType);
        boolean preserveAllSemiColons = false;
        boolean disableOptimizations = false;
        int linebreakpos = -1;
        boolean verbose = false;
        compressor.compress(out, linebreakpos, munge, verbose, preserveAllSemiColons, disableOptimizations);
    }

    public static class Options {
        @Option(name = "-cp", aliases = "--classpath", usage = "directory containing JavaScript class files (multiple directories can be separated by colon)", required = true)
        private String classPath;

        @Option(name = "-s", aliases = "--source-file", usage = "HTML source files to be processed (optional)", required = false)
        private List<File> sourceFiles = new LinkedList<>();

        @Option(name = "-c", aliases = "--main-class", usage = "main classes of the application (optional)", required = false)
        private List<String> mainClasses = new LinkedList<>();

        @Option(name = "-e", aliases = "--exclude-class", usage = "classes to be excluded with all its dependencies (optional)", required = false)
        private List<String> excludeClasses = new LinkedList<>();

        @Argument
        private List<String> arguments = new ArrayList<>();

        @Option(name = "-o", aliases = "--output-file", usage = "Path to output file", required = false)
        private File outputFile;

        @Option(name = "-C", aliases = "--compress", usage = "compress JS using YUI compressor (possible values: none, nomangle, full)")
        private CompressionType compressionType = CompressionType.none;

        @Option(name = "-u", aliases = "--use-strict", usage = "add \"use strict\"; at the beginning of the script")
        private boolean useStrict;

        public String getClassPath() {
            return classPath;
        }

        public void setClassPath(String classPath) {
            this.classPath = classPath;
        }

        public File getOutputFile() {
            return outputFile;
        }

        public void setOutputFile(File outputFile) {
            this.outputFile = outputFile;
        }

        public List<File> getSourceFiles() {
            return sourceFiles;
        }

        public void setSourceFiles(List<File> sourceFiles) {
            this.sourceFiles = sourceFiles;
        }

        public List<String> getMainClasses() {
            return mainClasses;
        }

        public void setMainClasses(List<String> mainClasses) {
            this.mainClasses = mainClasses;
        }

        public List<String> getExcludeClasses() {
            return excludeClasses;
        }

        public void setExcludeClasses(List<String> excludeClasses) {
            this.excludeClasses = excludeClasses;
        }

        public List<String> getArguments() {
            return arguments;
        }

        public void setArguments(List<String> arguments) {
            this.arguments = arguments;
        }

        public CompressionType getCompressionType() {
            return compressionType;
        }

        public void setCompressionType(CompressionType compressionType) {
            this.compressionType = compressionType;
        }

        public boolean isUseStrict() {
            return useStrict;
        }

        public void setUseStrict(boolean useStrict) {
            this.useStrict = useStrict;
        }
    }

    public enum CompressionType {
        none,
        nomangle,
        full
    }

    private static class LogErrorReporter implements ErrorReporter {

        public void warning(String message, String sourceName, int line, String lineSource, int lineOffset) {
            if (line < 0) {
                LOG.warn(message);
            } else {
                LOG.warn("{}:{}:{}", new Object[]{line, lineOffset, message});
            }
        }

        public void error(String message, String sourceName, int line, String lineSource, int lineOffset) {
            if (line < 0) {
                LOG.error(message);
            } else {
                LOG.error("{}:{}:{}", new Object[]{line, lineOffset, message});
            }
        }

        public EvaluatorException runtimeError(String message, String sourceName, int line, String lineSource, int lineOffset) {
            error(message, sourceName, line, lineSource, lineOffset);
            return new EvaluatorException(message);
        }
    }
}
